
 /**
  * LICENCIA LGPL:
  * 
  * Esta librería es Software Libre; Usted puede redistribuirla y/o modificarla
  * bajo los términos de la GNU Lesser General Public License (LGPL) tal y como 
  * ha sido publicada por la Free Software Foundation; o bien la versión 2.1 de 
  * la Licencia, o (a su elección) cualquier versión posterior.
  * 
  * Esta librería se distribuye con la esperanza de que sea útil, pero SIN 
  * NINGUNA GARANTÍA; tampoco las implícitas garantías de MERCANTILIDAD o 
  * ADECUACIÓN A UN PROPÓSITO PARTICULAR. Consulte la GNU Lesser General Public 
  * License (LGPL) para más detalles
  * 
  * Usted debe recibir una copia de la GNU Lesser General Public License (LGPL) 
  * junto con esta librería; si no es así, escriba a la Free Software Foundation 
  * Inc. 51 Franklin Street, 5º Piso, Boston, MA 02110-1301, USA o consulte
  * <http://www.gnu.org/licenses/>.
  *
  * Copyright 2011 Agencia de Tecnología y Certificación Electrónica
  */
 package es.accv.arangi.base.certificate.validation;
 
 import java.io.BufferedOutputStream;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.Vector;
 
 import org.apache.log4j.Logger;
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.X509Extensions;
 import org.bouncycastle.ocsp.CertificateID;
 import org.bouncycastle.ocsp.OCSPException;
 import org.bouncycastle.ocsp.OCSPReq;
 import org.bouncycastle.ocsp.OCSPReqGenerator;
 import org.bouncycastle.ocsp.OCSPResp;
 import org.bouncycastle.ocsp.OCSPRespStatus;
 
 import es.accv.arangi.base.ArangiObject;
 import es.accv.arangi.base.certificate.Certificate;
 import es.accv.arangi.base.exception.certificate.NormalizeCertificateException;
 import es.accv.arangi.base.exception.certificate.validation.MalformedOCSPResponseException;
 import es.accv.arangi.base.exception.certificate.validation.OCSPRequestGenerationException;
 import es.accv.arangi.base.exception.certificate.validation.OCSPServerConnectionException;
 import es.accv.arangi.base.exception.certificate.validation.OCSPValidateException;
 import es.accv.arangi.base.exception.device.OpeningDeviceException;
 import es.accv.arangi.base.util.Util;
 import es.accv.arangi.base.util.validation.ValidationResult;
 
 /**
  * Clase que representa un cliente OCSP. Los formatos de las llamadas al OCSP y de
  * sus respuestas siguen el estándar definido en la <a href="http://www.ietf.org/rfc/rfc2560.txt" target="rfc">RFC-2560</a><br><br>
  * 
  * Para validar un certificado:<br><br>
  * <code>
  *  CAList caList = new CAList (new File ("/listCA"));<br>
  *  URL url = new URL ("http://server/ocsp");<br>
  * 	OCSPClient ocsp = new OCSPClient (url, caList);<br><br>
  * 
  * 	Certificate certificate = new Certificate (new File ("c:/certificates/myCertificate.cer"));<br>
  * 	Certificate issuer = new Certificate (new File ("c:/certificates/myCertificateIssuer.cer"));<br><br>
  * 
  * 	int result = ocsp.validate (certificate, issuer);
  * </code>
  * 
  * @author <a href="mailto:jgutierrez@accv.es">José M Gutiérrez</a>
  */
 public class OCSPClient extends ArangiObject {
 
 	/*
 	 * Class logger
 	 */
 	static Logger logger = Logger.getLogger(OCSPClient.class);
 	
 	/*
 	 * OCSP's URL
 	 */
 	URL urlOCSP;
 	
 	/*
 	 * List of CA certificates (useful to validate OCSP response signature)
 	 */
 	CAList caList;
 	
 	/*
 	 * Name that identifies the requestor in the OCSP (normally has null value)
 	 */
 	String requestorName;
 	
 	/*
 	 * Keystore to sign requests
 	 */
 	KeyStore keyStore;
 	
 	/*
 	 * Alias name associated with a private key inside the keystore. Private key is
 	 * used to sign the OCSP request
 	 */
 	String alias;
 	
 	/*
 	 * Private key password
 	 */
 	String aliasPassword;
 	
 	/**
 	 * Constructor con todos los parámetros posibles para realizar la llamada. Normalmente
 	 * no es necesario crear las llamadas firmadas o con requestor name, por lo que sólo
 	 * se debe usar este constructor para algún OCSP especial.
 	 * 
 	 * @param urlOCSP URL al OCSP
 	 * @param caList Lista de certificados para validar la firma de la respuesta OCSP
 	 * @param requestorName Nombre que identifica al llamador en el OCSP
 	 * @param keystoreFile Fichero keystore
 	 * @param keystoreType Tipo de keystore (JKS o PKCS12)
 	 * @param keystorePassword Contraseña del keystore
 	 * @param alias Alias donde se encuentra la clave privada con la que firmar las
 	 * 	llamadas OCSP
 	 * @param aliasPassword Contraseña de la clave privada
 	 * @throws OpeningDeviceException No se puede abrir el keystore o el alias no existe
 	 */
 	public OCSPClient(URL urlOCSP, CAList caList, String requestorName, File keystoreFile, 
 			String keystoreType, String keystorePassword, String alias, String aliasPassword) throws OpeningDeviceException {
 		
 		//-- Load keystore
 		try {
 			keyStore = KeyStore.getInstance(keystoreType, CRYPTOGRAPHIC_PROVIDER);
 		} catch (KeyStoreException e) {
 			logger.info("[OCSPClient]::Cannot instantiate a keystore with type '" + keystoreType + "'", e);
 			throw new OpeningDeviceException ("Cannot instantiate a keystore with type '" + keystoreType + "'", e);
 		} 
 		FileInputStream fis = null;
 		try {
 			fis = new FileInputStream (keystoreFile);
 			keyStore.load(fis, keystorePassword.toCharArray());
 		} catch (FileNotFoundException e) {
 			logger.error("[OCSPClient]::File not found (" + keystoreFile + ")", e);
 			throw new OpeningDeviceException ("File not found (" + keystoreFile + ")", e);
 		} catch (NoSuchAlgorithmException e) {
 			logger.info("[OCSPClient]::The algorithm used to check the integrity of the keystore cannot be found", e);
 			throw new OpeningDeviceException ("The algorithm used to check the integrity of the keystore cannot be found", e);
 		} catch (CertificateException e) {
 			logger.info("[OCSPClient]::Any of the certificates in the keystore could not be loaded", e);
 			throw new OpeningDeviceException ("Any of the certificates in the keystore could not be loaded", e);
 		} catch (IOException e) {
 			logger.info("[OCSPClient]::there is an I/O or format problem with the keystore data", e);
 			throw new OpeningDeviceException ("there is an I/O or format problem with the keystore data", e);
 		} finally {
 			if (fis != null) {
 				try {
 					fis.close();
 				} catch (IOException e) {
 				}
 			}
 		}
 		
 		//-- Check if alias exists
 		try {
 			if (!keyStore.containsAlias(alias)) {
 				logger.info("[OCSPClient]::The alias '" + alias + "' is not present in the keystore");
 				throw new OpeningDeviceException ("The alias '" + alias + "' is not present in the keystore");
 			}
 		} catch (KeyStoreException e) {
 			logger.info("[OCSPClient]::The keystore has not been initialized (loaded)", e);
 			throw new OpeningDeviceException ("The keystore has not been initialized (loaded)", e);
 		}
 		
 		//-- Load the rest of parameters
 		this.urlOCSP = urlOCSP;
 		this.caList = caList;
 		this.requestorName = requestorName;
 		this.alias = alias;
 		this.aliasPassword = aliasPassword;
 		
 	}
 	
 	/**
 	 * Constructor con requestorName.
 	 * 
 	 * @param urlOCSP URL al OCSP
 	 * @param caList Lista de certificados para validar la firma de la respuesta OCSP
 	 * @param requestorName Nombre que identifica al llamador en el OCSP
 	 */
 	public OCSPClient(URL urlOCSP, CAList caList, String requestorName) {
 		
 		this.urlOCSP = urlOCSP;
 		this.caList = caList;
 		this.requestorName = requestorName;
 		
 	}
 	
 	/**
 	 * Constructor simple: es el caso más habitual
 	 * 
 	 * @param urlOCSP URL al OCSP
 	 * @param caList Lista de certificados para validar la firma de la respuesta OCSP
 	 */
 	public OCSPClient(URL urlOCSP, CAList caList) {
 		this (urlOCSP, caList, null);
 	}
 	
 	/**
 	 * Valida el certificado llamando al OCSP.
 	 * 
 	 * @param certificate Certificado a validar
 	 * @return int Resultado de la validación
 	 * @throws OCSPRequestGenerationException Errores generando la petición OCSP, probablemente
 	 * 	debido a los parámetros pasados a éste método
 	 * @throws OCSPServerConnectionException Errores en la conexión y respuesta del servidor OCSP
 	 * @throws MalformedOCSPResponseException La respuesta del servidor OCSP no es una respuesta
 	 * 	OCSP bien formada
 	 * @throws OCSPValidateException la respuesta no es válida (mal firmada o caducada)
 	 */
    	public int validate (ValidateCertificate certificate) throws OCSPServerConnectionException, MalformedOCSPResponseException, OCSPRequestGenerationException, OCSPValidateException {
    		return validate(certificate, new Date());
    	}
    		
 	/**
 	 * Valida el certificado llamando al OCSP.<br><br>
 	 * 
 	 * Nota: la mayoría de Autoridades de Certificación eliminan la información de revocación
 	 * de los certificados caducados de sus CRLs. En estos casos la validación histórica no
 	 * es posible y Arangi devolverá que el certificado era válido, ya que tanto la CRL como
 	 * el OCSP devuelven este resultado. La única forma de realizar validación histórica sería 
 	 * dentro de un proceso de firma longeva.
 	 * 
 	 * @param certificate Certificado a validar
 	 * @param validationDate Fecha en que se realizará la validación
 	 * @return int Resultado de la validación
 	 * @throws OCSPRequestGenerationException Errores generando la petición OCSP, probablemente
 	 * 	debido a los parámetros pasados a éste método
 	 * @throws OCSPServerConnectionException Errores en la conexión y respuesta del servidor OCSP
 	 * @throws MalformedOCSPResponseException La respuesta del servidor OCSP no es una respuesta
 	 * 	OCSP bien formada
 	 * @throws OCSPValidateException la respuesta no es válida (mal firmada o caducada)
 	 */
    	public int validate (ValidateCertificate certificate, Date validationDate) throws OCSPServerConnectionException, MalformedOCSPResponseException, OCSPRequestGenerationException, OCSPValidateException {
    		return validate (certificate, certificate.getIssuerCertificate(), validationDate);
    	}
    	
 	/**
 	 * Valida el certificado llamando al OCSP.
 	 * 
 	 * @param certificate Certificado a validar
 	 * @param issuerCertificate Certificado emisor
 	 * @return int Resultado de la validación
 	 * @throws OCSPRequestGenerationException Errores generando la petición OCSP, probablemente
 	 * 	debido a los parámetros pasados a éste método
 	 * @throws OCSPServerConnectionException Errores en la conexión y respuesta del servidor OCSP
 	 * @throws MalformedOCSPResponseException La respuesta del servidor OCSP no es una respuesta
 	 * 	OCSP bien formada
 	 * @throws OCSPValidateException la respuesta no es válida (mal firmada o caducada)
 	 */
    	public int validate (Certificate certificate, Certificate issuerCertificate) throws OCSPServerConnectionException, MalformedOCSPResponseException, OCSPRequestGenerationException, OCSPValidateException {
    		return validate(certificate, issuerCertificate, new Date());
    	}
    		
 	/**
 	 * Valida el certificado llamando al OCSP.<br><br>
 	 * 
 	 * Nota: la mayoría de Autoridades de Certificación eliminan la información de revocación
 	 * de los certificados caducados de sus CRLs. En estos casos la validación histórica no
 	 * es posible y Arangi devolverá que el certificado era válido, ya que tanto la CRL como
 	 * el OCSP devuelven este resultado. La única forma de realizar validación histórica sería 
 	 * dentro de un proceso de firma longeva.
 	 * 
 	 * @param certificate Certificado a validar
 	 * @param issuerCertificate Certificado emisor
 	 * @param validationDate Fecha en que se realizará la validación
 	 * @return int Resultado de la validación
 	 * @throws OCSPRequestGenerationException Errores generando la petición OCSP, probablemente
 	 * 	debido a los parámetros pasados a éste método
 	 * @throws OCSPServerConnectionException Errores en la conexión y respuesta del servidor OCSP
 	 * @throws MalformedOCSPResponseException La respuesta del servidor OCSP no es una respuesta
 	 * 	OCSP bien formada
 	 * @throws OCSPValidateException la respuesta no es válida (mal firmada o caducada)
 	 */
    	public int validate (Certificate certificate, Certificate issuerCertificate, Date validationDate) throws OCSPServerConnectionException, MalformedOCSPResponseException, OCSPRequestGenerationException, OCSPValidateException {
    		return validate (new Certificate[] { certificate }, new Certificate [] { issuerCertificate }, validationDate) [0];
    	}
    	
 	/**
 	 * Valida los certificados llamando al OCSP.<br><br>
 	 * 
 	 * Nota: la mayoría de Autoridades de Certificación eliminan la información de revocación
 	 * de los certificados caducados de sus CRLs. En estos casos la validación histórica no
 	 * es posible y Arangi devolverá que el certificado era válido, ya que tanto la CRL como
 	 * el OCSP devuelven este resultado. La única forma de realizar validación histórica sería 
 	 * dentro de un proceso de firma longeva.
 	 * 
 	 * @param certificates Certificados a validar
 	 * @param validationDate Fecha en que se realizará la validación
 	 * @return int Resultado de la validación
 	 * @throws OCSPRequestGenerationException Errores generando la petición OCSP, probablemente
 	 * 	debido a los parámetros pasados a éste método
 	 * @throws OCSPServerConnectionException Errores en la conexión y respuesta del servidor OCSP
 	 * @throws MalformedOCSPResponseException La respuesta del servidor OCSP no es una respuesta
 	 * 	OCSP bien formada
 	 * @throws OCSPValidateException la respuesta no es válida (mal firmada o caducada)
 	 * @throws NormalizeCertificateException No es posible normalizar alguno de los certificados 
 	 * 	de acuerdo al proveedor criptográfico de Arangí
 	 */
    	public int[] validate (ValidateCertificate[] certificates, Date validationDate) throws OCSPServerConnectionException, MalformedOCSPResponseException, OCSPRequestGenerationException, OCSPValidateException, NormalizeCertificateException {
    		
    		X509Certificate[] x509Certificates = new X509Certificate [certificates.length];
    		X509Certificate[] x509Issuers = new X509Certificate [certificates.length];
    		for (int i = 0; i < certificates.length; i++) {
    			x509Certificates[i] = certificates[i].toX509Certificate();
    			x509Issuers[i] = certificates[i].getIssuerCertificate().toX509Certificate();
 		}
    		return validate (x509Certificates, x509Issuers, validationDate);
    	}
    	
 	/**
 	 * Valida los certificados llamando al OCSP.
 	 * 
 	 * @param certificates Certificados a validar
 	 * @param issuerCertificates Certificados emisores
 	 * @return int Resultado de la validación
 	 * @throws OCSPRequestGenerationException Errores generando la petición OCSP, probablemente
 	 * 	debido a los parámetros pasados a éste método
 	 * @throws OCSPServerConnectionException Errores en la conexión y respuesta del servidor OCSP
 	 * @throws MalformedOCSPResponseException La respuesta del servidor OCSP no es una respuesta
 	 * 	OCSP bien formada
 	 * @throws OCSPValidateException la respuesta no es válida (mal firmada o caducada)
 	 * @throws NormalizeCertificateException No es posible normalizar alguno de los certificados 
 	 * 	de acuerdo al proveedor criptográfico de Arangí
 	 */
    	public int[] validate (X509Certificate[] certificates, X509Certificate[] issuerCertificates) throws OCSPServerConnectionException, MalformedOCSPResponseException, OCSPRequestGenerationException, OCSPValidateException, NormalizeCertificateException {
    		return validate (certificates, issuerCertificates, new Date());
    	}
    	
 	/**
 	 * Valida los certificados llamando al OCSP.
 	 * 
 	 * Nota: la mayoría de Autoridades de Certificación eliminan la información de revocación
 	 * de los certificados caducados de sus CRLs. En estos casos la validación histórica no
 	 * es posible y Arangi devolverá que el certificado era válido, ya que tanto la CRL como
 	 * el OCSP devuelven este resultado. La única forma de realizar validación histórica sería 
 	 * dentro de un proceso de firma longeva.
 	 * 
 	 * @param certificates Certificados a validar
 	 * @param issuerCertificates Certificados emisores
 	 * @param validationDate Fecha en que se realizará la validación
 	 * @return int Resultado de la validación
 	 * @throws OCSPRequestGenerationException Errores generando la petición OCSP, probablemente
 	 * 	debido a los parámetros pasados a éste método
 	 * @throws OCSPServerConnectionException Errores en la conexión y respuesta del servidor OCSP
 	 * @throws MalformedOCSPResponseException La respuesta del servidor OCSP no es una respuesta
 	 * 	OCSP bien formada
 	 * @throws OCSPValidateException la respuesta no es válida (mal firmada o caducada)
 	 * @throws NormalizeCertificateException No es posible normalizar alguno de los certificados 
 	 * 	de acuerdo al proveedor criptográfico de Arangí
 	 */
    	public int[] validate (X509Certificate[] certificates, X509Certificate[] issuerCertificates, Date validationDate) throws OCSPServerConnectionException, MalformedOCSPResponseException, OCSPRequestGenerationException, OCSPValidateException, NormalizeCertificateException {
    		Certificate[] arangiCertificates = new Certificate [certificates.length];
    		Certificate[] arangiIssuers = new Certificate [certificates.length];
    		for (int i = 0; i < certificates.length; i++) {
    			arangiCertificates[i] = new Certificate (certificates[i]);
    			arangiIssuers[i] = new Certificate (issuerCertificates[i]);
 		}
    		return validate (arangiCertificates, arangiIssuers, validationDate);
    	}
    		
 	/**
 	 * Valida los certificados llamando al OCSP.<br><br>
 	 * 
 	 * @param certificates Certificados a validar
 	 * @param issuerCertificates Certificados emisores
 	 * @return int Resultado de la validación
 	 * @throws OCSPRequestGenerationException Errores generando la petición OCSP, probablemente
 	 * 	debido a los parámetros pasados a éste método
 	 * @throws OCSPServerConnectionException Errores en la conexión y respuesta del servidor OCSP
 	 * @throws MalformedOCSPResponseException La respuesta del servidor OCSP no es una respuesta
 	 * 	OCSP bien formada
 	 * @throws OCSPValidateException la respuesta no es válida (mal firmada o caducada)
 	 */
    	public int[] validate (Certificate[] certificates, Certificate[] issuerCertificates) throws OCSPServerConnectionException, MalformedOCSPResponseException, OCSPRequestGenerationException, OCSPValidateException {
    		return validate (certificates, issuerCertificates, new Date());
    	}
    	
 	/**
 	 * Valida los certificados llamando al OCSP.<br><br>
 	 * 
 	 * Nota: la mayoría de Autoridades de Certificación eliminan la información de revocación
 	 * de los certificados caducados de sus CRLs. En estos casos la validación histórica no
 	 * es posible y Arangi devolverá que el certificado era válido, ya que tanto la CRL como
 	 * el OCSP devuelven este resultado. La única forma de realizar validación histórica sería 
 	 * dentro de un proceso de firma longeva.
 	 * 
 	 * @param certificates Certificados a validar
 	 * @param issuerCertificates Certificados emisores
 	 * @param validationDate Fecha en que se realizará la validación
 	 * @return int Resultado de la validación
 	 * @throws OCSPRequestGenerationException Errores generando la petición OCSP, probablemente
 	 * 	debido a los parámetros pasados a éste método
 	 * @throws OCSPServerConnectionException Errores en la conexión y respuesta del servidor OCSP
 	 * @throws MalformedOCSPResponseException La respuesta del servidor OCSP no es una respuesta
 	 * 	OCSP bien formada
 	 * @throws OCSPValidateException la respuesta no es válida (mal firmada o caducada)
 	 */
    	public int[] validate (Certificate[] certificates, Certificate[] issuerCertificates, Date validationDate) throws OCSPServerConnectionException, MalformedOCSPResponseException, OCSPRequestGenerationException, OCSPValidateException {
    		
    		logger.debug("[OCSPClient.validate] :: Start :: " + Arrays.asList(new Object[] { certificates, issuerCertificates, validationDate }));
    		
    		//-- Obtener la respuesta OCSP
    		OCSPResponse oscpResponse = getOCSPResponse(certificates, issuerCertificates);
    		
      	if (!oscpResponse.isSignatureValid ()) {
    			logger.info ("[OCSPClient.validate] :: La firma de la respuesta OCSP no es correcta");
    			throw new OCSPValidateException ("La firma de la respuesta OCSP no es correcta");
      	}
 		
      	//-- Objeto resultados
      	int [] result = new int [certificates.length];
      	
      	//-- Obtener resultados
      	CertificateOCSPResponse[] singleResponses = oscpResponse.getSingleResponses();
      	for (int i = 0; i < singleResponses.length; i++) {
 			if(singleResponses[i] != null) {
 				
 				//-- Check validity interval
 				if (singleResponses[i].getValidityPeriodEnd() != null && singleResponses[i].getValidityPeriodEnd().before(new Date())) {
 		   			logger.error ("[OCSPClient.validate] :: La respuesta OCSP ha superado su periodo de validez, que acababa el " + Util.dateFormatAccurate.format(singleResponses[i].getValidityPeriodEnd()));
 		   			throw new OCSPValidateException ("La respuesta OCSP ha superado su periodo de validez, que acababa el " + Util.dateFormatAccurate.format(singleResponses[i].getValidityPeriodEnd()));
 				}
 				
 				//-- Return result
 		     	if(singleResponses[i].getStatus() != ValidationResult.RESULT_CERTIFICATE_REVOKED){
 		     		//-- Certificate valid or unknown
 		     		result [i] = singleResponses[i].getStatus();
 		     		
 		     	}else{
 		     		//-- Certificate is revoked: mirar la fecha de revocación y compararla con la fecha en que se quiere la validación
 	     			if (singleResponses[i].getRevocationTime().before(validationDate) || singleResponses[i].getRevocationTime().equals(validationDate)) {
 		     			//-- Certificate revoked
 	     				result [i] =  ValidationResult.RESULT_CERTIFICATE_REVOKED;
 	     			} else {
 			     		//-- Certificate revoked, but revokation is after the validation date
 	     				result [i] =  ValidationResult.RESULT_VALID;
 	     			}
 		     	}			
 			}
 			
 		}
      	
      	//-- Devolver resultado
      	return result;
 	}
 
 	/**
 	 * Obtiene una respuesta del OCSP. En caso de que el OCSP devuelva una respuesta con
 	 * errores, éstos se transformarán en una excepción OCSPException. <br><br>
 	 * 
 	 * Esta respuesta puede ser utilizada dentro del proceso de creación de una firma longeva.
 	 * 
 	 * @param certificate Certificado a validar
 	 * @return Respuesta del OCSP
 	 * @throws NormalizeCertificateException Alguno de los certificados no ha podido ser 
 	 * 	normalizado al proveedor criptográfico de Arangí
 	 * @throws OCSPRequestGenerationException Errores generando la petición OCSP, probablemente
 	 * 	debido a los parámetros pasados a éste método
 	 * @throws OCSPServerConnectionException Errores en la conexión y respuesta del servidor OCSP
 	 * @throws MalformedOCSPResponseException La respuesta del servidor OCSP no es una respuesta
 	 * 	OCSP bien formada
 	 */
    	public OCSPResponse getOCSPResponse (ValidateCertificate certificate) throws OCSPServerConnectionException, MalformedOCSPResponseException, OCSPRequestGenerationException, NormalizeCertificateException {
    		
    		return getOCSPResponse(certificate.toX509Certificate(), certificate.getIssuerCertificate().toX509Certificate());
    	}
    	
 	/**
 	 * Obtiene una respuesta del OCSP. En caso de que el OCSP devuelva una respuesta con
 	 * errores, éstos se transformarán en una excepción OCSPException. <br><br>
 	 * 
 	 * Esta respuesta puede ser utilizada dentro del proceso de creación de una firma longeva.
 	 * 
 	 * @param certificate Certificado a validar
 	 * @param issuerCertificate Certificado emisor
 	 * @return Respuesta del OCSP
 	 * @throws NormalizeCertificateException Alguno de los certificados no ha podido ser 
 	 * 	normalizado al proveedor criptográfico de Arangí
 	 * @throws OCSPRequestGenerationException Errores generando la petición OCSP, probablemente
 	 * 	debido a los parámetros pasados a éste método
 	 * @throws OCSPServerConnectionException Errores en la conexión y respuesta del servidor OCSP
 	 * @throws MalformedOCSPResponseException La respuesta del servidor OCSP no es una respuesta
 	 * 	OCSP bien formada
 	 */
    	public OCSPResponse getOCSPResponse (X509Certificate certificate, X509Certificate issuerCertificate) throws OCSPServerConnectionException, MalformedOCSPResponseException, OCSPRequestGenerationException, NormalizeCertificateException {
    		return getOCSPResponse(new Certificate (certificate), new Certificate (issuerCertificate));
    	}
    		
 	/**
 	 * Obtiene una respuesta del OCSP. En caso de que el OCSP devuelva una respuesta con
 	 * errores, éstos se transformarán en una excepción OCSPException. <br><br>
 	 * 
 	 * Esta respuesta puede ser utilizada dentro del proceso de creación de una firma longeva.
 	 * 
 	 * @param certificate Certificado a validar
 	 * @param issuerCertificate Certificado emisor
 	 * @return Respuesta del OCSP
 	 * @throws OCSPRequestGenerationException Errores generando la petición OCSP, probablemente
 	 * 	debido a los parámetros pasados a éste método
 	 * @throws OCSPServerConnectionException Errores en la conexión y respuesta del servidor OCSP
 	 * @throws MalformedOCSPResponseException La respuesta del servidor OCSP no es una respuesta
 	 * 	OCSP bien formada
 	 */
    	public OCSPResponse getOCSPResponse (Certificate certificate, Certificate issuerCertificate) throws OCSPServerConnectionException, MalformedOCSPResponseException, OCSPRequestGenerationException {
    		return getOCSPResponse(new Certificate[] {certificate}, new Certificate [] {issuerCertificate});
    	}
    		
 	/**
 	 * Obtiene una respuesta del OCSP. En caso de que el OCSP devuelva una respuesta con
 	 * errores, éstos se transformarán en una excepción OCSPException. <br><br>
 	 * 
 	 * Esta respuesta puede ser utilizada dentro del proceso de creación de una firma longeva.
 	 * 
 	 * @param certificates Certificados a validar
 	 * @return Respuesta del OCSP
 	 * @throws OCSPRequestGenerationException Errores generando la petición OCSP, probablemente
 	 * 	debido a los parámetros pasados a éste método
 	 * @throws OCSPServerConnectionException Errores en la conexión y respuesta del servidor OCSP
 	 * @throws MalformedOCSPResponseException La respuesta del servidor OCSP no es una respuesta
 	 * 	OCSP bien formada
 	 */
    	public OCSPResponse getOCSPResponse (ValidateCertificate[] certificates) throws OCSPServerConnectionException, MalformedOCSPResponseException, OCSPRequestGenerationException {
    		
    		Certificate[] issuerCertificates = new Certificate [certificates.length];
    		for (int i = 0; i < certificates.length; i++) {
 			issuerCertificates[i] = certificates[i].getIssuerCertificate();
 		}
    		
    		return getOCSPResponse(certificates, issuerCertificates);
    	}
    	
 	/**
 	 * Obtiene una respuesta del OCSP. En caso de que el OCSP devuelva una respuesta con
 	 * errores, éstos se transformarán en una excepción OCSPException. <br><br>
 	 * 
 	 * Esta respuesta puede ser utilizada dentro del proceso de creación de una firma longeva.
 	 * 
 	 * @param certificates Certificados a validar (debe tener el mismo tamaño que issuerCertificates)
 	 * @param issuerCertificates Certificados emisores (debe tener el mismo tamaño que 
 	 * 	certificates)
 	 * @return Respuesta del OCSP
 	 * @throws OCSPRequestGenerationException Errores generando la petición OCSP, probablemente
 	 * 	debido a los parámetros pasados a éste método
 	 * @throws OCSPServerConnectionException Errores en la conexión y respuesta del servidor OCSP
 	 * @throws MalformedOCSPResponseException La respuesta del servidor OCSP no es una respuesta
 	 * 	OCSP bien formada
 	 */
    	public OCSPResponse getOCSPResponse (Certificate[] certificates, Certificate[] issuerCertificates) throws OCSPServerConnectionException, MalformedOCSPResponseException, OCSPRequestGenerationException {
    		
    		logger.debug("[OCSPClient.getOCSPResponse] :: Entrada");
    		
    		//-- If certificate is null throw an exception
    		if (certificates == null || certificates.length == 0) {
    			logger.info ("[OCSPClient.getOCSPResponse] :: No hay certificados a validar");
    			throw new OCSPRequestGenerationException ("No hay certificados a validar");
    		}
    		
    		//-- If certificate issuer is null throw an exception
    		if (issuerCertificates == null || issuerCertificates.length == 0) {
    			logger.info ("[OCSPClient.getOCSPResponse] :: No hay certificados emisores para realizar la validación");
    			throw new OCSPRequestGenerationException ("No hay certificados emisores para realizar la validación");
    		}
    		
    		//-- If certificate issuer is null throw an exception
    		if (certificates.length != issuerCertificates.length) {
    			logger.info ("[OCSPClient.getOCSPResponse] :: El tamaño de los arrays de certificados y emisores es diferente");
    			throw new OCSPRequestGenerationException ("El tamaño de los arrays de certificados y emisores es diferente");
    		}
    		
    		//-- Generate the OCSP request
    		byte[] ocspRequest = generateOCSPRequest(certificates,	issuerCertificates, requestorName, keyStore, alias, aliasPassword);
    		
    		//-- Init variables		
 		DataOutputStream lOut = null;
 		HttpURLConnection httpConnection;
 		try {
 			httpConnection = (HttpURLConnection) urlOCSP.openConnection();
 		} catch (IOException e) {
    			logger.info ("[OCSPClient.getOCSPResponse] :: Unable to connect to " + urlOCSP, e);
    			throw new OCSPServerConnectionException ("Unable to connect to " + urlOCSP, e);
 		}
 		
 		// Setup the HTTP request			
 		httpConnection.setRequestProperty("Content-Type", "application/ocsp-request");	
 		httpConnection.setRequestProperty("Accept", "application/ocsp-response");
 		
 		httpConnection.setDoOutput(true);
         
         // Send the request
         try {
 			lOut = new DataOutputStream(new BufferedOutputStream (httpConnection.getOutputStream()));
 	        lOut.write(ocspRequest, 0, ocspRequest.length);
 	        lOut.flush();
 	        lOut.close();
 		} catch (IOException e) {
    			logger.info ("[OCSPClient.getOCSPResponse] :: Error sending the OCSP request to: " + urlOCSP, e);
    			throw new OCSPServerConnectionException ("Error sending the OCSP request to: " + urlOCSP, e);
 		}	        
         
         // Check the HTTP response	        
         int lIntResponseCode;
 		try {
 			lIntResponseCode = httpConnection.getResponseCode();
 		} catch (IOException e) {
    			logger.info ("[OCSPClient.getOCSPResponse] :: Error getting the OCSP response code", e);
    			throw new OCSPServerConnectionException ("Error getting the OCSP response code", e);
 		}
         String 	lStrContentType = httpConnection.getContentType();
         if (lIntResponseCode==200 && lStrContentType.equalsIgnoreCase("application/ocsp-response")) {	        
 	        // Process the result             
 	        int lLngLongitud = httpConnection.getContentLength();
 	        
 	        DataInputStream lObjData;
 	        byte[] lBytRespuesta;
 			try {
 				lObjData = new DataInputStream(httpConnection.getInputStream());
 		        lBytRespuesta = new byte[lLngLongitud];
 		     	lObjData.readFully(lBytRespuesta, 0, lBytRespuesta.length);	        
 		     	lObjData.close();
 		     	
 		     	// Get the response from OCSP
 		     	OCSPResp ocspResponse = new OCSPResp(lBytRespuesta);
 		     	if (ocspResponse == null) {
 		     		throw new OCSPServerConnectionException ("OCSP response is a null value");
 		     	}
 		     	
 		     	// Get possible error message
 		     	if (ocspResponse.getStatus() == OCSPRespStatus.TRY_LATER) {
 		     		throw new OCSPException ("The OCSP response indicates that it will be try later");
 		     	}
 		     	if (ocspResponse.getStatus() == OCSPRespStatus.INTERNAL_ERROR) {
 		     		throw new OCSPException ("The OCSP response indicates that there is an internal error in OCSP server");
 		     	}
 		     	if (ocspResponse.getStatus() == OCSPRespStatus.MALFORMED_REQUEST) {
 		     		throw new OCSPException ("The OCSP response indicates that the request is malformed");
 		     	}
 		     	if (ocspResponse.getStatus() == OCSPRespStatus.SIGREQUIRED) {
 		     		throw new OCSPException ("The OCSP response indicates that the request must be signed");
 		     	}
 		     	if (ocspResponse.getStatus() == OCSPRespStatus.UNAUTHORIZED) {
 		     		throw new OCSPException ("The OCSP response indicates that the request must contain authorization information");
 		     	}
 		     	
 		     	//-- Devolver la respuesta
 		     	return new OCSPResponse (ocspResponse);
 		     	
 			} catch (IOException e) {
 	   			logger.info ("[OCSPClient.getOCSPResponse] :: Cannot get the OCSP response", e);
 	   			throw new OCSPServerConnectionException ("Cannot get the the OCSP response", e);
 			} catch (OCSPException e) {
 	   			logger.info ("[OCSPClient.getOCSPResponse] :: Cannot get the OCSP basic response", e);
 	   			throw new OCSPServerConnectionException ("Cannot get the the OCSP basic response", e);
 			}
 	     	
         } else {
    			logger.info ("[OCSPClient.getOCSPResponse] :: HTTP response code is " + lIntResponseCode);
    			throw new OCSPServerConnectionException ("HTTP response code is " + lIntResponseCode);
         }
 	    
 	}
 
 	/**
 	 * Genera una petición OCSP.
 	 * 
 	 * @param certificate Certificado a validar
 	 * @param issuerCertificate Certificado emisor
 	 * @return Array de bytes con la petición OCSP
 	 * @throws Exception Errores durante la generación
 	 */
    	public static  byte[] generateOCSPRequest(Certificate certificate, Certificate issuerCertificate) throws Exception{
    		
    		return generateOCSPRequest(new Certificate [] { certificate }, new Certificate [] { issuerCertificate });
    		
    	}
    	
    	/**
 	 * Genera una petición OCSP múltiple: se validarán varios certificados al mismo tiempo.
 	 * 
 	 * @param certificates Certificados a validar
 	 * @param issuerCertificates Emisores de los certificados
 	 * @return Array de bytes con la petición OCSP
 	 * @throws Exception Errores durante la generación
 	 */
    	public static  byte[] generateOCSPRequest(Certificate [] certificates, Certificate [] issuerCertificates) throws Exception{
    		
    		return OCSPClient.generateOCSPRequest(certificates, issuerCertificates, null, null, null, null);
    		
    	}
    	
    	/**
 	 * Genera una petición OCSP múltiple: se validarán varios certificados al mismo tiempo.
 	 * La petición ser firmará.
    	 * 
 	 * @param certificates Certificados a validar
 	 * @param issuerCertificates Emisores de los certificados
 	 * @param requestorName Nombre que identifica al llamador en el OCSP
 	 * @param keystore Keystore abierto que contiene la clave privada que firma la petición
 	 * @param alias Alias del keystore para firmar la petición
 	 * @param aliasPassword Contraseña de la clave privada
 	 * @return Array de bytes con la petición OCSP
    	 * @throws OCSPRequestGenerationException Errores durante la generación
    	 */
    	public static  byte[] generateOCSPRequest(Certificate [] certificates, Certificate [] issuerCertificates,
    			String requestorName, KeyStore keystore, String alias, String aliasPassword) throws OCSPRequestGenerationException{
 		
    		//-- Validate parameters
    		if (certificates == null || certificates.length == 0) {
    			throw new OCSPRequestGenerationException ("List of certificates to validate is empty");
    		}
    		if (issuerCertificates == null || issuerCertificates.length == 0) {
    			throw new OCSPRequestGenerationException ("List of CA certificates to validate is empty");
    		}
    		if (certificates.length != issuerCertificates.length) {
    			throw new OCSPRequestGenerationException ("List of certificates and CA certificates must have the same number of elements");
    		}
    		
    		OCSPReq lObjPeticionOCSP;
 		OCSPReqGenerator lObjGeneradorPeticion;
 		
 		CertificateID lObjIdentificadorCert;
 
 		// Preparing the request
 		lObjGeneradorPeticion = new OCSPReqGenerator();
 		
 		// We only accepts the basic response
 //		Vector oids = new Vector();
 //        Vector values = new Vector();
 //        oids.add(OCSPObjectIdentifiers.id_pkix_ocsp_response);
 //        values.add(new X509Extension(false, new	DEROctetString(OCSPObjectIdentifiers.id_pkix_ocsp_basic)));
 //        oids.add(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
 //        values.add(new X509Extension(false, new	DEROctetString("mi_nonce".getBytes())));
 //        lObjGeneradorPeticion.setRequestExtensions(new	X509Extensions(oids, values));
         
         for (int i=0;i<certificates.length;i++) {
 			// Generating the certificateId for request
 			try {
 				lObjIdentificadorCert = new CertificateID(CertificateID.HASH_SHA1, issuerCertificates [i].toX509Certificate() , certificates[i].getSerialNumberBigInteger());
 			} catch (OCSPException e) {
 	   			logger.info ("[OCSPClient.generateOCSPRequest] :: Cannot create a new certificate ID for the OCSP request", e);
 	   			throw new OCSPRequestGenerationException ("Cannot create a new certificate ID for the OCSP request", e);
 			} 
 			
 			// Adding the request certificate 
 			Vector oids = new Vector(); 
 			Vector values = new Vector();
 	        X509Extensions extensions = new X509Extensions(oids, values);
 			lObjGeneradorPeticion.addRequest(lObjIdentificadorCert);
         }
         
         // Requestor name
         if (requestorName != null) {
         	lObjGeneradorPeticion.setRequestorName(new GeneralName (GeneralName.rfc822Name, requestorName));
         }
 		
 		// Create the request
 		try {
 			if (keystore == null) {
 				lObjPeticionOCSP = lObjGeneradorPeticion.generate();
 			} else {
 				lObjPeticionOCSP = lObjGeneradorPeticion.generate("SHA1withRSA", (PrivateKey) keystore.getKey(alias, aliasPassword.toCharArray()),
 						null, CRYPTOGRAPHIC_PROVIDER_NAME);
 			}
 		} catch (Exception e) {
    			logger.info ("[OCSPClient.generateOCSPRequest] :: Cannot create a new OCSP request", e);
    			throw new OCSPRequestGenerationException ("Cannot create a new OCSP request", e);
 		} 
 		
 		// Devolvemos la peticion codificada.
 		try {
 			return lObjPeticionOCSP.getEncoded();
 		} catch (IOException e) {
    			logger.info ("[OCSPClient.generateOCSPRequest] :: Cannot encode the OCSP request", e);
    			throw new OCSPRequestGenerationException ("Cannot encode the OCSP request", e);
 		}
 			
 		
 	}
 
    	/**
    	 * Obtiene la URL de este OCSP
    	 * 
    	 * @return URL de este OCSP
    	 */
 	public URL getURL() {
 		return this.urlOCSP;
 	}
 	
 	//-- Métodos privados
 	
 
 
 }